package com.danikula.videocache.defaultStrategy;

import com.danikula.videocache.GetRequest;
import com.danikula.videocache.HttpUrlSource;
import com.danikula.videocache.ProxyCache;
import com.danikula.videocache.ProxyCacheException;
import com.danikula.videocache.util.ProxyCacheUtils;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

import static com.danikula.videocache.util.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;


/**
 * {@link ProxyCache} that read http url and writes data to {@link Socket}
 *
 * @author Alexey Danilov ().
 */
public class HttpProxyCache extends ProxyCache {
    private static final float NO_CACHE_BARRIER = .2f;

    private final HttpUrlSource source;
    private final FileCache cache;
    private final DefaultCacheStrategy cacheStrategy;


    public HttpProxyCache(HttpUrlSource source, FileCache cache, DefaultCacheStrategy cacheStrategy) {
        super(source, cache);
        this.cache = cache;
        this.source = source;
        this.cacheStrategy = cacheStrategy;
    }


    @Override
    public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException,
            InterruptedException {
        OutputStream out = new BufferedOutputStream(socket.getOutputStream());
        String responseHeaders = newResponseHeaders(request);
        out.write(responseHeaders.getBytes("UTF-8"));
        long offset = request.rangeOffset;
        if (isUseCache(request)) {
            responseWithCache(out, offset);
        } else {
            responseWithoutCache(out, offset);
        }
    }

    /**
     * 是否使用边下载边缓存
     *
     * @param request Model for Http GET request.
     * @return 是否使用边下载边缓存
     * @throws ProxyCacheException 代理缓存错误
     */
    protected boolean isUseCache(GetRequest request) throws ProxyCacheException {
        long sourceLength = source.length();
        boolean sourceLengthKnown = sourceLength > 0;
        long cacheAvailable = cache.available();
        // 如果设置了缓冲比例 那么使用设置的缓冲比例
        if (cacheStrategy != null && cacheStrategy.getCacheBufferSize() <= 0
                && cacheStrategy.getCacheBufferRate() > 0) {
            return !sourceLengthKnown || !request.partial
                    || request.rangeOffset <= cacheAvailable + sourceLength * cacheStrategy.getCacheBufferRate();
        } else if (cacheStrategy != null && cacheStrategy.getCacheBufferSize() > 0
                && cacheStrategy.getCacheBufferRate() <= 0) {
            // 如果设置了缓冲大小 那么使用设置的缓冲大小
            return !sourceLengthKnown || !request.partial
                    || request.rangeOffset <= cacheAvailable + cacheStrategy.getCacheBufferSize();
        } else {
            // 否则使用默认的 当前缓存大小加视频大小的20%作为缓冲区大小
            return !sourceLengthKnown || !request.partial
                    || request.rangeOffset <= cacheAvailable + sourceLength * NO_CACHE_BARRIER;
        }
    }

    @Override
    public int read(byte[] buffer, long offset, int length) throws ProxyCacheException {
        ProxyCacheUtils.assertBuffer(buffer, offset, length);

        while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped) {
            readSourceAsync();
            waitForSourceData();
            checkReadSourceErrorsCount();
        }
        int read = cache.read(buffer, offset, length);
        if (cache.isCompleted() && percentsAvailable != 100) {
            percentsAvailable = 100;
            onCachePercentsAvailableChanged(100);
        }
        return read;
    }

    /**
     * 读取网络数据并返回给socket 不做缓存
     *
     * @param out    输出流
     * @param offset 视频数据读取起点
     * @throws ProxyCacheException 代理缓存错误
     * @throws IOException         IO错误
     */
    protected void responseWithoutCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
        HttpUrlSource newSourceNoCache = new HttpUrlSource(this.source);
        try {
            long tempOffset = offset;
            newSourceNoCache.open((int) tempOffset);
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int readBytes;
            while ((readBytes = newSourceNoCache.read(buffer)) != -1) {
                out.write(buffer, 0, readBytes);
                tempOffset += readBytes;
            }
            out.flush();
        } finally {
            newSourceNoCache.close();
        }
    }

    @Override
    protected void onCachePercentsAvailableChanged(int percents) {
        if (listener != null) {
            listener.onCacheAvailable(cache.file, source.getUrl(), percents);
        }
    }

    @Override
    protected Runnable getRunable() {
        return new SourceReaderRunnable();
    }

    private class SourceReaderRunnable implements Runnable {
        long sourceAvailable = -1;
        long offset = 0;

        @Override
        public void run() {
            readSource();
        }

        private void readSource() {
            try {
                beginHtppRequestData();
            } catch (ProxyCacheException e) {
                readSourceErrorsCount.incrementAndGet();
                onError(e);
            } finally {
                closeSource();
                notifyNewCacheDataAvailable(offset, sourceAvailable);
            }
        }

        private void beginHtppRequestData() throws ProxyCacheException {
            offset = cache.available();
            source.open(offset);
            sourceAvailable = source.length();
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int readBytes;
            while ((readBytes = source.read(buffer)) != -1) {
                synchronized (stopLock) {
                    if (isStopped()) {
                        return;
                    }
                    cache.append(buffer, readBytes);
                }
                offset += readBytes;
                notifyNewCacheDataAvailable(offset, sourceAvailable);
            }
            tryComplete();
            onSourceRead();
        }
    }


}
